9 对象的扩展(ECMAScript6入门)

9.1 属性(包括方法)的简洁表示法

9.2.1 基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
var birth = '2000/01/01';

var Person = {

name: '张三',

//等同于birth: birth
birth,

// 等同于hello: function ()...
hello() { console.log('我的名字是', this.name); }

};

9.2.2 应用

  1. 函数中返回对象
1
2
3
4
5
6
7
8
function getPoint() {
var x = 1;
var y = 10;
return {x, y};
}

getPoint()
// {x:1, y:10}
  1. CommonJS模块输出变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var ms = {};

function getItem (key) {
return key in ms ? ms[key] : null;
}

function setItem (key, value) {
ms[key] = value;
}

function clear () {
ms = {};
}

module.exports = { getItem, setItem, clear };
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
  1. 属性的赋值器(setter)和取值器(getter)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var cart = {
_wheels: 4,

get wheels () {
return this._wheels;
},

set wheels (value) {
if (value < this._wheels) {
throw new Error('数值太小了!');
}
this._wheels = value;
}
}
  1. Generator函数(前面需要加上*)
1
2
3
4
5
var obj = {
* m(){
yield 'hello world';
}
};

9.2 属性名表达式

说明:定义对象字面量时,属性名(包括方法名)也可以使用[表达式]这种形式,即属性名表达式
注意:属性名表达式简洁表示法,不能同时使用,会报错

9.2.1 案例

属性名(非方法)

1
2
3
4
5
6
7
8
9
10
var lastWord = 'last word';

var a = {
'first word': 'hello',
[lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"

方法名

1
2
3
4
5
6
7
let obj = {
['h'+'ello']() {
return 'hi';
}
};

obj.hello() // hi

9.2.2 属性名表达式与简洁表示法

1
2
3
4
5
6
7
8
// 报错
var foo = 'bar';
var bar = 'abc';
var baz = { [foo] };

// 正确
var foo = 'bar';
var baz = { [foo]: 'abc'};

9.3 方法的name属性

分类 name属性值
普通函数 函数名
普通对象方法 方法名
get 方法 get 方法名
set 方法 set 方法名
bind 返回的方法 bound 函数名
Function 构造函数创建的函数 anonymous
Symbol 值 Synbol 值的描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* 1. 普通对象方法和 get 方法 */
var person = {
// 普通对象方法
sayName() {
console.log(this.name);
},
// get 方法
get firstName() {
return "Nicholas";
}
};

person.sayName.name // "sayName"
person.firstName.name // "get firstName"

/* 2. Function 构造函数创建的函数 */
(new Function()).name // "anonymous"

/* 3. bind 返回的方法 */
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"

/* 4. Symbol 值 */
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""

9.4 Object.is()

背景:JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等

  • ==:会自动转换数据类型
  • ===:NaN不等于自身,以及+0等于-0

    用途:用来比较两个值是否严格相等

1
2
3
4
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

9.4.1 Object.is() vs. ===

不同点 Object.is() ===
NaNNaN比较 相等 不相等
+0-0比较 不相等 相等
1
2
3
4
5
+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

9.4.2 polyfill

1
2
3
4
5
6
7
8
9
10
Object.defineProperty(Object, 'is', {
value: function (x, y) {
if (x === y) {
// 处理 +0 不等于 -0的情况
return x !== 0 --|| 1 / x === 1 / y;--
}
// 处理 NaN 的情况
return x !== x && y !== y;
}
});

9.5 Object.assign()

用途:对象的合并
说明:将源对象(source)的所有可枚举属性,复制到目标对象(target)
参数(+):第一个参数是目标对象,后面的参数都是源对象
限制:Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false
注意:属性名为Symbol值的属性,也会被Object.assign拷贝

9.5.1 第一个参数(目标对象)

情形 Object.assign()行为
只有第一个参数 直接返回该参数
不是对象且能转成对象 会先转成对象
undefinednull 无法转成对象,会报错

9.5.2 其他参数(源对象)

情形 Object.assign()行为
非对象且可以转成对象 转成对象
非对象且无法转成对象 会跳过(不参与合并)
1
2
3
4
5
6
var v1 = 'abc';
var v2 = true;
var v3 = 10;

var obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

非对象如何转为对象?

基本类型 转换后 是否有可枚举的实例属性
布尔 Boolean
数字 Number
字符串 Array
1
2
3
4
5
6
// 包装对象的内部属性[[PrimitiveValue]]上面,这个属性是不会被Object.assign拷贝的
Object(true) // {[[PrimitiveValue]]: true}
Object(10) // {[[PrimitiveValue]]: 10}

// 只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]:

限制

1
2
3
4
5
6
7
Object.assign({b: 'c'},
Object.defineProperty({}, 'invisible', {
enumerable: false,
value: 'hello'
})
)
// { b: 'c' }

Symbol 值

1
2
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }

9.5.3 注意点

浅拷贝

object.assign()实行的是浅拷贝。如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用

1
2
3
4
5
var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

替换同名属性

合并时一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加

1
2
3
4
var target = { a: { b: 'c', d: 'e' } }
var source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }

合并数组

Object.assign可以用来处理数组,但是会把数组视为对象

1
2
3
// 把数组视为属性名为0、1、2的对象
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

9.5.4 常见用途

(1)为对象添加属性

1
2
3
4
5
6
7
8

class Point {
// 构造器
constructor(x, y) {
// 通过Object.assign方法,将x属性和y属性添加到Point类的对象实
Object.assign(this, {x, y})
}
}

(2)为对象添加方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});

// SomeClass.prototype.someMethod = function (arg1, arg2) {
// ···
//};
// SomeClass.prototype.anotherMethod = function () {
// ···
//};

(3) 克隆对象

说明:将原始对象拷贝到一个空对象{},就得到了原始对象的克隆
注意:采用这种方法克隆,只能克隆原始对象自身的值,不能克隆它通过prototype继承的值

1
2
3
4
5
6
7
8
9
10
11
12
13
// 只克隆自身,不克隆原型链
function cloneWithoutProto(origin) {
return Object.assign({}, origin);
}

// 克隆自身并保持原型链
function cloneWithProto(origin) {
// 获取 prototype
let originProto = Object.getPrototypeof(origin);

// 以 originProto 为原型创建新对象,然后将克隆目标自身的值克隆过来
return Object.assign(Object.create(originProto), origin);
}

(4) 合并多个对象

1
2
3
4
5
6
/**
* 将多个对象合并到某个对象
* @param {Object} target 目的对象
* @param {Object} sources 源对象
*/

const merge = (target, ...sources) => Object.assign(target, ...sources);

(5) 为属性指定默认值

说明:Object.assign方法将DEFAULTSoptions合并成一个新对象,如果两者有同名属性,则option的属性值会覆盖DEFAULTS的属性值

1
2
3
4
5
6
7
8
9
10
// 默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};

// 应用默认值
function processContent(options) {
let options = Object.assign({}, DEFAULTS, options);
}

9.6 属性的可枚举性

9.6.1 Object.getOwnPropertyDescriptor()

描述对象:对象的每个属性都有一个描述对象,用来控制该属性的行为
说明:获取该属性的描述对象

1
2
3
4
5
6
7
8
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }

9.6.2 可枚举性

enumerable如果该属性为false,就表示某些操作会忽略当前属性
注意:ES6规定,所有Class的原型的方法都是不可枚举的
技巧:操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替

会被enumerable影响的操作 说明
for...in 只遍历对象自身的和继承的可枚举的属性
Object.keys() 返回对象自身的所有可枚举的属性的键名
JSON.stringify() 只串行化对象自身的可枚举的属性
Object.assign() 会忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性

Object.create(proto[, propertiesObject])

用途: 以 第一个参数为 prototype 创建一个新的对象,其中第二个参数中可以为新对象添加些初始化的属性。
注意:第二个参数默认是不可遍历的,除非在描述对象中显示声明属性可枚举。

9.7 属性的遍历

说明:一共 5 种方式
遍历顺序:这5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则

  • 首先遍历所有属性名为数值的属性,按照数字排序
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序
  • 最后遍历所有属性名为Symbol值的属性,按照生成时间排序
方式 说明 备注
for...in 循环遍历对象自身的和继承的可枚举属性 不含Symbol属性
Object.keys(obj) 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
Object.getOwnPropertyNames(obj) 返回一个数组,包含对象自身的所有属性 不含Symbol属性,但是包括不可枚举属性
Object.getOwnPropertySymbols(obj) 返回一个数组,包含对象自身的所有Symbol属性
Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举
1
2
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

9.8 proto属性,Object.setPrototypeOf(),Object.getProptotypeOf()

9.8.1 __proto__属性

__proto__属性:用来读取或设置当前对象的prototype对象
注意:该属性没有写入ES6的正文,而是写入了附录。标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。

1
2
3
4
5
6
7
8
9
// es6的写法
var obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;

// es5的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };

polyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Object.defineProperty(Object.prototype, '__proto__', {
get() {
let _thisObj = Object(this);
return Object.getPrototypeOf(_thisObj);
},
set(proto) {
if (this === undefined --|| this === null) {--
throw new TypeError();
}
if (!isObject(this)) {
return undefined;
}
if (!isObject(proto)) {
return undefined;
}
let status = Reflect.setPrototypeOf(this, proto);
if (!status) {
throw new TypeError();
}
},
});
function isObject(value) {
return Object(value) === value;
}

9.8.2 Object.setPrototypeOf()

用途:设置一个对象的prototype对象
注意:ES6正式推荐的设置原型对象的方法

Object.setPrototypeOf(object, prototype)

1
2
3
4
5
6
7
8
9
10
11
12
// 例一:创建一个新对象,并将其 prototype 设置为 null
var o = Object.setPrototypeOf({}, null);

// 例二:将 proto 对象设为obj对象的原型,所以从obj对象可以读取 proto 对象的属性
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40

polyfill

1
2
3
4
function (obj, proto) {
obj.__proto__ = proto;
return obj;
}

9.8.3 Object.getPrototypeOf()

用途:读取一个对象的prototype对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 构造器
function Rectangle() {
}

// 创建实例
var rec = new Rectangle();

// 改变 prototype 之前
Object.getPrototypeOf(rec) === Rectangle.prototype // true

// 改变 prototype
Object.setPrototypeOf(rec, Object.prototype);

// 改变 prototype 之后
Object.getPrototypeOf(rec) === Rectangle.prototype // false

9.9 Object.values(),Object.entries()

比较 返回值 兼容性
Object.keys() 一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名 es5
Object.values() 一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值 es7提案
Object.entries() 一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组 es7提案

9.9.1 Object.keys()

1
2
3
var obj = { foo: "bar", baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

9.9.2 Object.values()

注意:

  • 返回数组的成员顺序,见9.7
  • 会过滤属性名为Symbol值的属性
  • 如果参数是一个字符串,会返回各个字符组成的一个数组
  • 如果参数不是对象,会先将其转为对象
  • 由于数值和布尔值的包装对象,都不会为实例添加非继承的属性。所以会返回空数组
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 只返回对象自身的可遍历属性 */
var obj = Object.create({}, {p: {value: 42}});
Object.values(obj) // []

/* 会过滤属性名为Symbol值的属性 */
Object.values({ [Symbol()]: 123, foo: 'abc' }); // ['abc']

/* 参数是一个字符串,会返回各个字符组成的一个数组 */
Object.values('foo') // ['f', 'o', 'o']

/* 数值和布尔值会返回空数组 */
Object.values(42) // []
Object.values(true) // []

9.9.3 Object.entries()

说明:除了返回值不一样,该方法的行为与Object.values基本一致。
用途:

  • 基本用途是遍历对象的属性
    • 将对象转为真正的Map结构
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 基本用途是遍历对象的属性 */
let obj = { one: 1, two: 2 };
for (let [k, v] of Object.entries(obj)) {
console.log(`${JSON.stringify(k)}: ${JSON.stringify(v)}`);
}
// "one": 1
// "two": 2


/* 将对象转为真正的Map结构 */
var obj = { foo: 'bar', baz: 42 };
var map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }

polyfill

Generator函数的版本

1
2
3
4
5
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}

Generator函数的版本

1
2
3
4
5
6
7
function entries(obj) {
let arr = [];
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]]);
}
return arr;
}

9.10 对象的扩展运算符

说明:Rest解构赋值/扩展运算符(...)引入对象
兼容性:ES7的一个提案,Babel转码器已经支持这项功能

9.10.1 Rest解构赋值

说明:对象的Rest解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面

  • Rest解构赋值要求等号右边是一个对象,所以如果等号右边是undefinednull,就会报错(因为它们无法转为对象)
  • Rest解构赋值必须是最后一个参数,否则会报错
  • Rest解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么Rest解构赋值拷贝的是这个值的引用,而不是这个值的副本
  • Rest解构赋值不会拷贝继承自原型对象的属性

    用途:扩展某个函数的参数,引入其他操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* 基础 */
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

/* Rest 解构赋值要求等号右边是一个对象,所以如果等号右边是 undefined 或 null ,就会报错(因为它们无法转为对象) */
let { x, y, ...z } = null; // 运行时错误
let { x, y, ...z } = undefined; // 运行时错误

/* Rest解构赋值必须是最后一个参数,否则会报错 */
let { ...x, y, z } = obj; // 句法错误
let { x, ...y, ...z } = obj; // 句法错误

/* Rest解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组、对象、函数)、那么`Rest解构赋值`拷贝的是这个值的引用,而不是这个值的副本 */
let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

/* Rest解构赋值不会拷贝继承自原型对象的属性 */
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let o3 = { ...o2 };
o3 // { b: 2 }


/* 扩展某个函数的参数 */
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// 使用x和y参数进行操作
// 其余参数传给原始函数
return baseFunction(restConfig);
}

9.10.2 扩展运算符

说明:可以取代Object.assign()的功能
用途:用于取出参数对象的所有可遍历属性,拷贝到当前对象之中

  • 可以用于合并两个对象
  • 自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉
  • 扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的
  • 如果扩展运算符的参数是 null 或 undefined,这个两个值会被忽略,不会报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* 可以用于合并两个对象 */
let ab = { ...a, ...b };
// 等同于
// let ab = Object.assign({}, a, b);

/* 自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉 */
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
// let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
// let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
// let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });

/* 把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值 */
let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
// let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
// let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);

/* 扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的 */
// 并不会抛出错误,因为x属性只是被定义,但没执行
let aWithXGetter = {
...a,
get x() {
throws new Error('not thrown yet');
}
};

// 会抛出错误,因为x属性被执行了
let runtimeError = {
...a,
...{
get x() {
throws new Error('thrown now');
}
}
};

/* 如果扩展运算符的参数是null或undefined,这个两个值会被忽略,不会报错 */
let emptyObject = { ...null, ...undefined }; // 不报错

9.11 Object.getOwnPropertyDescriptors()

兼容性:ES7的一个提案
用途:返回指定对象所有自身属性(非继承属性)的描述对象

  • 解决Object.assign()无法正确拷贝get属性和set属性的问题(Object.assign()方法总是拷贝一个属性的值,而不会拷贝它背后的getset
  • 配合Object.create()方法,将对象属性克隆到一个新对象。这属于浅拷贝
  • 可以实现,一个对象继承另一个对象
  • 可以用来实现Mixin(混入)模式

返回值描述:所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象
注意:出于完整性的考虑,Object.getOwnPropertyDescriptors进入标准以后,还会有Reflect.getOwnPropertyDescriptors方法

俩方法比较 兼容性 说明
Object.getOwnPropertyDescriptor es5 返回某个对象属性的描述对象(包括继承来的)
Object.getOwnPropertyDescriptors es7 返回指定对象所有自身属性(非继承属性)的描述对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {
foo: 123,
get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: bar],
// set: undefined,
// enumerable: true,
// configurable: true } }

9.11.1 polyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 返回某个对象自身属性的描述对象(不包括继承来的)
* @param {Object} obj 目标对象
* @return {Object} 返回一个对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象
*/

function getOwnPropertyDescriptors(obj) {
const result = {};
// 只遍历自身的属性
for (let key of Reflect.ownKeys(obj)) {
result[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return result;
}

9.11.2 常见用途

9.11.2.1 拷贝 get 和 set

背景:Object.assign()方法总是拷贝一个属性的值,而不会拷贝它背后的getset
说明:解决Object.assign()无法正确拷贝get属性和set属性的问题
原理:Object.getOwnPropertyDescriptors()配合Object.defineProperties

使用Object.assign()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const source = {
set foo(value) {
console.log(value);
}
};

const target1 = {};
Object.assign(target1, source);

// foo 的 value 是 undefined
Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
// writable: true,
// enumerable: true,
// configurable: true }

正确拷贝(浅拷贝) get 和 set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const source = {
set foo(value) {
console.log(value);
}
};

const target2 = {};
// 先获取原始对象的描述对象,再定义到目标对象中
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));

Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
// set: [Function: foo],
// enumerable: true,
// configurable: true }

9.11.2.2 配合 Object.create() 实现浅拷贝

1
2
3
4
5
6
7
/**
* 以愿对象的 prototype 作为 prototype 创建新的对象
*/

const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);

9.11.2.3 实现继承

1
2
3
4
5
6
const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
})
);

9.11.2.4 实现Mixin(混入)模式

说明:利用Array.prototype.reduce将某些对象的属性混合到目标对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let mix = (object) => ({
/**
* 将某些对象的属性混合到目标对象
* @{Array} mixins 要混合的源对象组成的数组
*/

with: (...mixins) => mixins.reduce(
(c, mixin) => Object.create(
c, Object.getOwnPropertyDescriptors(mixin)
), object)
});

// multiple mixins example
let a = {a: 'a'};
let b = {b: 'b'};
let c = {c: 'c'};
let d = mix(c).with(a, b);// 将 a, b 混合到 c